﻿//************************************************************************************************************
// CoNatural.Threading.dll
// Author: Roger Torres
//************************************************************************************************************
// You can use, modify, and distribute the source code and executable programs based on the source code.
// This source code is provided "as is" and without warranties as to performance or merchantability. 
// The author and/or distributors of this source code may have made statements about this source code. 
// Any such statements do not constitute warranties and shall not be relied on by the user in deciding 
// whether to use this source code. This source code is provided without any express or implied warranties 
// whatsoever. Because of the diversity of conditions and hardware under which this source code may be used, 
// no warranty of fitness for a particular purpose is offered. The user is advised to test the source code 
// thoroughly before relying on it. The user must assume the entire risk of using the source code.
//************************************************************************************************************

using System;
using System.Threading;

namespace CoNatural.Threading.Async {
   /// <summary>
   /// Base abstract class representing asynchronous actions
   /// </summary>
   public abstract class AsyncAction {
      /// <summary>
      /// Gets the name of the action
      /// </summary>
      public string Name { get; private set; }

      /// <summary>
      /// Flags the action to raise completion and progress events
      /// </summary>
      public bool RaiseEvents { get; private set; }

      /// <summary>
      /// Constructor
      /// </summary>
      /// <param name="name">The name of the action.</param>
      /// <param name="raiseEvents">Flag to raise completion and progress events.</param>
      protected AsyncAction(string name, bool raiseEvents) {
         Name = name;
         RaiseEvents = raiseEvents;
      }

      /// <summary>
      /// Called internally to post action completed event.
      /// </summary>
      /// <param name="cancelled">True if action was cancelled.</param>
      /// <param name="error">Exception raised when executing action or null.</param>
      internal void Complete(bool cancelled, AsyncActionException error) {
         Context.OnAsyncActionCompleted(this, cancelled, error);
      }

      /// <summary>
      /// Called internally to post action progress events.
      /// </summary>
      /// <param name="progressPercentage">Progress percentage.</param>
      internal void ChangeProgress(int progressPercentage) {
         Context.OnAsyncActionProgressChanged(this, progressPercentage);
      }

      /// <summary>
      /// Called internally to post action trace events.
      /// </summary>
      /// <param name="message">The trace message.</param>
      internal void Trace(string message) {
         Context.OnAsyncActionTraced(this, message);
      }

      /// <summary>
      /// Context assigned to action by engine at runtime.
      /// </summary>
      internal AsyncContext Context { get; set; }

      /// <summary>
      /// Starts execution of the asynchronous operation in a dedicated ThreadPool thread.
      /// </summary>
      /// <param name="context">The execution context.</param>
      public virtual void Start(AsyncContext context) {
         context.OnStart(this);
         ThreadPool.QueueUserWorkItem((state) => { BeginInvoke(this, () => context.OnCompleted()); });
      }

      /// <summary>
      /// Actions are invoked by the runtime internally. Each action implements
      /// it's own logic according to their compositional properties.
      /// </summary>
      /// <param name="source">The async action invoking this method.</param>
      /// <param name="nextAction">Delegate to a method that will continue or complete the operation.</param>
      internal abstract void BeginInvoke(AsyncAction source, Action nextAction);
   }

   /// <summary>
   /// Asynchronous action wrapping the BeginXxx-EndXxx .NET async pattern.
   /// </summary>
   /// <typeparam name="T">The type of the result.</typeparam>
   public class AsyncAction<T> : AsyncAction {
      private Func<AsyncCallback, object, IAsyncResult> _beginXxx;
      private Func<IAsyncResult, T> _endXxx;

      /// <summary>
      /// State object assigned to IAsyncResult.
      /// </summary>
      public object AsyncState { get; private set; }

      /// <summary>
      /// Gets the result of the action.
      /// </summary>
      public T Result { get; private set; }

      /// <summary>
      /// Constructor.
      /// </summary>
      /// <param name="name">The name of the action.</param>
      /// <param name="raiseEvents">True to raise events, False otherwise.</param>
      /// <param name="beginXxx">The delegate to execute the BeginXxx method.</param>
      /// <param name="endXxx">The delegate to execute the EndXxx method.</param>
      public AsyncAction(string name, bool raiseEvents, Func<AsyncCallback, object, IAsyncResult> beginXxx, Func<IAsyncResult, T> endXxx)
         : base(name, raiseEvents) {
         _beginXxx = beginXxx;
         _endXxx = endXxx;
         AsyncState = null;
      }

      /// <summary>
      /// Invokes BeginXxx with callback delegate to EndXxx to resume action.
      /// Will also continue the operation in case of errors.
      /// </summary>
      /// <param name="source">The source async action.</param>
      /// <param name="nextAction">The delegate to continue the operation.</param>
      internal override void BeginInvoke(AsyncAction source, Action nextAction) {
         Context = source.Context;

         try {
            IAsyncResult asyncResult = _beginXxx(
               (ar) => {
                  bool cancelled = Context.CancelRequested;
                  try {
                     Result = _endXxx(ar);
                     Complete(cancelled, null);
                     
                     // Simulate random delays to test how threads are used
                     // Thread.Sleep(new Random().Next(200));
                  }
                  catch (Exception ex) {
                     Complete(cancelled, new AsyncActionException(this, string.Empty, ex));
                  }
                  nextAction();
               },
               null
            );
            AsyncState = asyncResult.AsyncState;
         }
         catch (Exception ex) {
            Complete(false, new AsyncActionException(this, string.Empty, ex));
            nextAction();
         }
      }
   }
}
